package jeopardy.server.game;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import jeopardy.common.protocol.game.play.AnswerMsg;
import jeopardy.common.protocol.game.play.CurrentUsersMsg;
import jeopardy.common.protocol.game.play.GameRecordMsg;
import jeopardy.common.protocol.game.play.NewQuestionMsg;
import jeopardy.common.protocol.game.play.NotifyStartMsg;
import jeopardy.common.protocol.game.play.QnAAttemptedMsg;
import jeopardy.common.protocol.game.play.QuestionAnswerMsg;

import org.apache.log4j.Logger;

public class Client{
	private static Logger log = Logger.getLogger(Client.class);
	
	private String userName = null;
	private Socket sock = null;
	private Game game = null;
	
	private Client myself = this;
	private boolean stillPlaying = false;
	
	private BlockingQueue<Serializable> toSendQueue = new LinkedBlockingQueue<Serializable>();
	
	/**
	 * Client class is used to represent a game player.  It will detect when the player disconnects (write to socket times out), and report to the game instance.
	 * Client class runs two separate threads for timely processing of messages:
	 * 	1.  Reader thread reads from the socket (the message that the game player is sending to the server).
	 * 		Turns out out that during gameplay, there's only one type of message that is valid from the client, and that is a GameAnswer class representing the answer submittal.
	 * 
	 *  2.  Writer thread writes messages to the socket.  Since writing to socket can take time (network), we don't want to block up the calling thread.
	 *      What Client class does, is using a LinkedBlockingQueue for queueing up the messages to write.  LinkedBlockingQueue has the "offer" method which 
	 *      	immediately returns, and "poll" method, which allows you to wait a specified amount of time.
	 *      Writer thread "polls" this queue, and as soon as a message shows up, it will write that to the socket, within its own thread. 
	 */
	public Client(String user, Socket s){
		sock = s;
		userName = user;
		myself = this;
		stillPlaying = true;	
	}
	
	public String getUserName(){
		return userName;
	}
	
	public void joinGame(Game g){
		this.game = g;
		Thread reader = new Thread(new Runnable(){
			@Override
			public void run(){
				try{
					ObjectInputStream ois = new ObjectInputStream(sock.getInputStream());
					while(stillPlaying){
						Object o = ois.readObject();
						if(o instanceof AnswerMsg){
							AnswerMsg am = (AnswerMsg) o;
							game.submitAnswer(new UserAnswer(myself.userName, am.getAnswer(), am.getQuestion()));
						}						
					}
					ois.close();
				}catch(Exception e){
					game.leaveGame(myself);
					stillPlaying = false;
				}
			}
		});
		reader.start();
		
		Thread writer = new Thread(new Runnable(){
			@Override
			public void run(){
				try{
					ObjectOutputStream oos = new ObjectOutputStream(sock.getOutputStream());
					while(stillPlaying){
						Serializable next = toSendQueue.poll(5, TimeUnit.SECONDS);
						if(next != null){
							oos.writeObject(next);
						}
					}
					oos.close();
				}catch(Exception e){
					game.leaveGame(myself);
					stillPlaying = false;
				}
			}
		});
		writer.start();
	}
	
	public void tellGetReady(Long startsInMilli, List<String> initialClients){
		NotifyStartMsg newMsg = new NotifyStartMsg(startsInMilli, initialClients.toArray(new String[0]));
		toSendQueue.offer(newMsg);
	}
	public void tellUserList(List<String> curUsers){
		toSendQueue.offer(new CurrentUsersMsg(curUsers.toArray(new String[0])));
	}
	public void tellFinished(GameRecordMsg rec) {
		toSendQueue.offer(rec);
	}
	public void tellQuestion(NewQuestionMsg q) {
		toSendQueue.offer(q);
		
	}
	public void tellQuestionStatus(String answer, String user, long timeTook, String[] eligiblePlayers) {
		QnAAttemptedMsg attempt = new QnAAttemptedMsg(answer, user, timeTook, eligiblePlayers);
		toSendQueue.offer(attempt);
	}
	public void tellQuestionFinished(QuestionAnswerMsg qa) {
		toSendQueue.offer(qa);
	}
}